feat: HN news reader with offline-first feed and premium UI#11
Merged
maximcoding merged 17 commits intomasterfrom Mar 25, 2026
Merged
feat: HN news reader with offline-first feed and premium UI#11maximcoding merged 17 commits intomasterfrom
maximcoding merged 17 commits intomasterfrom
Conversation
- Home screen: replaces demo feed with live Hacker News Algolia API (Zod schema + mapper → React Query nearRealtime + MMKV persister) - StoryScreen: full in-app WebView reader with back/close navigation, falls back to HN discussion page for link-less posts - Settings: premium redesign — glassmorphism-style profile card, icon badges per row (sun/moon/globe/info/logout), improved dividers - Auth: social login buttons with text labels, Apple icon viewport fix, subtitle wrapping fix, demo hint single-line, terms padding fix - Icons: add check, sun, moon, globe, info, logout, layers SVGs - i18n: updated home section labels across en/de/ru - Navigation: HOME_STORY route added to root stack param list - Install react-native-webview for in-app article reading - Fix Jest config: add react-native-webview to transformIgnorePatterns and add WebView mock to jest.setup.js Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ad code
- Replace magic numbers in StoryScreen with design token constants
(HEADER_HEIGHT=spacing.xxxxxl, ICON_SIZE=spacing.lg, PROGRESS_BAR_HEIGHT=spacing.xxs)
- Extract formatRelativeTime to shared/utils to eliminate duplication
between hn.mappers and useFeedQuery
- Rename ActivityType → AccentVariant; align values with theme color keys
('primary'/'info'/'warning' replacing 'task'/'message'/'alert')
- Add useMemo/useCallback throughout StoryScreen and useFeedQuery
- Add attachLogging(hnClient) for dev HTTP visibility
- Remove dead code: TAB_COMPONENTS route, UIKitScreen, redundant subtitle??undefined
- Add 14 unit tests for parseDomain and mapHnHitToFeedItem
- All 41 tests pass, zero TypeScript errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace JSX-based dynamic config with static config objects:
- Inline all thin stack wrappers (auth, onboarding, settings, home-tabs)
into a single root-navigator.tsx using createNativeStackNavigator({screens})
- Replace NavigationContainer + AppLayout with createStaticNavigation(RootStack)
- Add useT() to AnimatedTabBar for label derivation — no more tabBarLabel
prop needed in tab screen options
- Remove dead routes: HOME_STACK, HOME_TABS, SETTINGS_ROOT/LANGUAGE/THEME
- Delete 6 files: auth-stack, onboarding-stack, settings-stack, home-stack,
home-tabs, AppLayout
- Delete 3 dead feature param-list directories (types now inferred by RN)
Zero type errors, 41 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-fix 17 files: import organization, trailing commas, line wrapping. Zero lint errors, zero type errors, 41 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract useShimmer() → src/shared/hooks/useShimmer.ts Pure Animated.Value pulse hook, no feature coupling - Extract SectionHeader → src/shared/components/ui/SectionHeader.tsx Label row + optional offline/synced status pill; reusable by any list screen - HomeScreen imports both from shared; removes ~70 lines of inline code Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
showPassword, emailFocused, passFocused all replaced by useToggle() from shared/hooks. Toggle handler is now a stable ref (toggleShowPassword) instead of an inline arrow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate zLoginRequest validation in useLoginMutation
(AuthService.login already validates; hook was parsing twice)
- Remove hardcoded mutationKey ['auth','login'] — optional and not in authKeys
- Flatten nested offline guard: if (!mock) { if (offline) } → if (!mock && offline)
- Move SessionSchema out of useAuthSessionQuery into auth.schemas.ts
where all Zod schemas belong (zSessionResponse / SessionResponse)
- Remove .catch(() => undefined) on qc.cancelQueries() in useLogout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move useAppLaunch → src/session/ (imports from session layer) - Move useShimmer → src/features/home/hooks/ (only used in HomeScreen) - Delete useBackHandler from shared (zero consumers; superseded by navigation/helpers/use-back-handler) - Remove SESSION_RELATED_QUERY_TAGS from shared/constants (cross-feature coupling); replace with feature-local tag arrays in useLoginMutation and useUpdateProfile, each scoped to their own tag map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace TouchableOpacity with Pressable throughout (ScreenHeader,
AuthScreen) per react-native-skills guidelines; use style callback
for pressed opacity feedback instead of activeOpacity
- Remove activeOpacity={1} on social buttons — Reanimated handles
press animation; Pressable has no built-in feedback
- Extract static inline styles in HomeScreen to StyleSheet.create():
StoryCard meta row (rendered in FlashList on every item) and
ListFooter height (constant value)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Memoization: - ThemePickerModal/LanguagePickerModal: extract ThemeOptionRow/ LanguageOptionRow as memo'd components — onPress is now a stable useCallback([opt.mode/code, onSelect]) instead of a new closure created per render per map item - ThemeScreen: extract ModeButton memo component for same reason - LanguageScreen: replace 3 inline arrow functions with named useCallback handlers (handleEnglish/Russian/German) - StoryScreen: extract handleClose useCallback for the dismiss button Accessibility: - ThemePickerModal/LanguagePickerModal items: add accessibilityRole, accessibilityLabel (translated option name), accessibilityState - SettingsRow: add accessibilityRole (button when pressable, none otherwise) and accessibilityLabel derived from label prop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HalfSheet is a generic animated bottom-sheet UI primitive with no navigation knowledge — it imports only from shared/theme. Living in navigation/modals/ forced feature screens to import from navigation/ just to render a container component. - Move navigation/modals/half-sheet.tsx → shared/components/ui/HalfSheet.tsx - Update ThemePickerModal and LanguagePickerModal imports - Remove dead `export * from './modals/half-sheet'` in navigation/index.ts (default exports are not captured by export *, so it was a no-op) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same issue as HalfSheet: GlobalModal imports only from shared/theme and shared/components/ui — no navigation dependency. Feature screens that use it would have to import from navigation/ for a pure UI shell. - Move navigation/modals/global-modal.tsx → shared/components/ui/GlobalModal.tsx - Remove export from navigation/index.ts (no consumers, dead export) AnimatedTabBar stays in navigation/ — it depends on BottomTabBarProps, navigation.emit(), and ROUTES, making it genuine navigation infrastructure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ptions Naming: - BootstrapRoute → InitialRoute - getBootstrapRoute() → getInitialRoute() - useBootstrapRoute() → useInitialRoute() (file renamed accordingly) - All call sites updated: root-navigator, navigation-persistence, useAppLaunch Result: initialRouteName: getInitialRoute() reads as intended at the call site; no more indirection through a "bootstrap" name for what is simply the navigator's starting screen. Dead code: - Delete src/navigation/options/ entirely — useNav(), navigation.presets.ts, navigation.tokens.ts, tabOptions.tsx were all helpers for the old dynamic navigator config, superseded by the static config migration. Zero external consumers remained. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace stale dynamic-config references (NavigationContainer, per-feature param-list files, useBootstrapRoute) with current static config API - Add v7-specific rules: screens object, groups vs nesting, if property for conditional auth rendering, .with() for dynamic options - Add params rules: JSON-serializable, IDs only, reserved key list, setParams vs replaceParams, popTo for back-passing data - Add navigation action guide: navigate vs push vs popTo vs popToTop - Add screen lifecycle rules: useFocusEffect pattern, useIsFocused, addListener cleanup - Add TypeScript rules: type not interface, no useNavigation<T>(), CompositeScreenProps, NavigatorScreenParams - Update half-sheet entry to reference HalfSheet shared component - Add InteractionManager rule for post-navigation heavy work - Add empty-directory rule for navigation folder Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Navigation: - Revert root-navigator to dynamic JSX config (Stack.Navigator/Screen) with typed generic params; useInitialRoute() hook in component body - Add HomeTabParamList to root-param-list.ts; export from index.ts - Update NavigationRoot.tsx and AnimatedTabBar.tsx accordingly Theme: - Remove unused italic font variant from fonts.ts - Add brand.ts token file; export from theme index - Sync dark/light theme with new token Auth / User: - Export AUTH_SESSION_TAGS from auth/api/keys.ts (satisfies AuthTag[]) - Export USER_UPDATE_TAGS from user/api/keys.ts - Remove dead oauth-brand-colors.ts constant file - Update useLoginMutation and useUpdateProfile to import named tag arrays Docs / rules: - Update navigation.md to reflect dynamic config (not static) - Minor fixes in CLAUDE.md, react-query.md, shared-services.md, development.md - Remove rn-architect agent (superseded by rn-code-reviewer) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Brief description of the change.
Checklist
npm run lintpasses (Biome —biome check .)npm testpassesnpx tsc --noEmitpasses## Unreleased